/*
 * Copyright 2005 Anders Nyman.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sf.j2ep.servers;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.j2ep.model.Rule;
import net.sf.j2ep.model.Server;
import net.sf.j2ep.servers.ClusterContainer.ClusteredServer;
import net.sf.j2ep.servers.DispatchConfigerFactory.DispatchConfiger;

/**
 * 
 * ClassName: DispatchCluster description: A dispatch cluster using
 * dispatchColumn to dispatch in servers in the cluster. date: 2016年12月26日
 * 下午4:47:41
 * @descripton request.getParameter()、 request.getInputStream()、request.getReader()这三种方法是有冲突的，因为流只能被读一次。
 * 参考:http://www.cnblogs.com/xiancheng/p/5524338.html
 * @author usun
 * @version
 */
public class DispatchCluster extends ServerContainerBase {
	//
	public static final String REQUEST_STREAM_STRING = "REQUEST_STREAM_STRING";
	/**
	 * Logging element supplied by commons-logging.
	 */
	private static Log log;

	/**
	 * The servers in our cluster,
	 */
	protected HashMap servers;
	/**
	 * 分发实现类型[目录只支持redis]
	 */
	private String dispatchCat;

	/**
	 * 分发字段
	 */
	private String dispatchColumn;
	//集群server总数
	private int numberOfServers;

	public DispatchCluster() {
		servers = new HashMap();
		numberOfServers = 0;
		log = LogFactory.getLog(DispatchCluster.class);
	}

	

	public Server getServer(HttpServletRequest request) {
		String serverId = getServerIdFromCookie(request.getCookies());
		DispatchServer server = (DispatchServer) servers.get(serverId);
		if (server == null) {
			/*解决request.getParameter()、 request.getInputStream()、request.getReader()这三种方法是有冲突的，因为流只能被读一次。*/
			String reqStreamStr = requestStream2Str(request);
			Map<String,String> streamParams = getRequestStreamParam(reqStreamStr);
			String dispatchValue = streamParams.get(dispatchColumn);
			if (null == dispatchValue){
				dispatchValue = request.getParameter(dispatchColumn);
			}
			request.setAttribute(REQUEST_STREAM_STRING, reqStreamStr);
			DispatchConfiger configer = null;
			try {
				configer = DispatchConfigerFactory.factory(dispatchCat);
				server = (DispatchServer)servers.get(configer.getServerId(dispatchValue));
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else {
			log.debug("Server found in session");
		}
		return server;
	}
	
	/**
	 * 
	 * @description RequestStream to String
	 * @author usun
	 * @param request
	 * @return
	 */
	private String requestStream2Str(HttpServletRequest request){
		StringBuilder sb = new StringBuilder();   
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream()));   
			String line = null;   
			while ((line = br.readLine()) != null) {   
			    sb.append(line);   
			}
		} catch (IOException e) {
			e.printStackTrace();
		}   
		return sb.toString(); 
	}
	/**
	 * 
	 * @description post串解析为参数map
	 * @author usun
	 * @param params
	 * @return
	 */
	private Map<String,String> getRequestStreamParam(String params){   
        Map<String,String> paramMap = new HashMap<String,String>();;   
        if(null != params && params.trim().length() > 0){   
            String param[]=params.split("&");   
			for (int i = 0; i < param.length; i++) {
				String content = param[i];
				String key = content.substring(0, content.indexOf("="));
				String value = content.substring(content.indexOf("=") + 1, content.length());
				paramMap.put(key, value);   
           }   
        }   
        return paramMap;   
    }   

	/**
	 * Will create a new ClusteredServer and add it to the hash map.
	 * 
	 * @param domainName
	 *            The domain name for the new server
	 * @param directory
	 *            The director for the new server.
	 */
	public synchronized void addServer(String domainName, String directory, String serverId) {
		if (domainName == null) {
			throw new IllegalArgumentException("The domainName cannot be null");
		}
		if (directory == null) {
			directory = "";
		}

		DispatchServer server = createNewServer(domainName, directory, serverId);
		servers.put(server.getServerId(), server);
		log.debug("Added server " + domainName + directory + " to the cluster on id " + server.getServerId());
	}

	public Server getServerMapped(String link) {
		Iterator itr = servers.values().iterator();
		Server match = null;
		while (itr.hasNext() && match == null) {
			Server server = (Server) itr.next();
			String fullPath = server.getDomainName() + server.getPath() + "/";
			if (link.startsWith(fullPath)) {
				match = server;
			}
		}
		return match;
	}

	/**
	 * @see net.sf.j2ep.servers.ClusterContainer#createNewServer(java.lang.String,
	 *      java.lang.String)
	 */
	protected DispatchServer createNewServer(String domainName, String directory, String serverId) {
		if (null == serverId || serverId.trim().length() < 1) {
			serverId = "server" + numberOfServers;
			numberOfServers++;
		}
		
		DispatchServer server = new DispatchServer(domainName, directory, serverId);
		return server;
	}

	/**
	 * Locates any specification of which server that issued a session. If there
	 * is no session or the session isn't mapped to a specific server null is
	 * returned.
	 * 
	 * @param cookies
	 *            The cookies so look for a session in
	 * @return the server's ID or null if no server is found
	 */
	private String getServerIdFromCookie(Cookie[] cookies) {
		String serverId = null;
		if (cookies != null) {
			for (int i = 0; i < cookies.length; i++) {
				Cookie cookie = cookies[i];
				if (isSessionCookie(cookie.getName())) {
					String value = cookie.getValue();
					String id = value.substring(value.indexOf(".") + 1);
					if (id.startsWith("server")) {
						serverId = id;
					}
				}
			}
		}
		return serverId;
	}

	/**
	 * Checks if the supplied name of a cookie is known to be a session.
	 * 
	 * @param name
	 *            The cookies name
	 * @return true if this cookie is specifying a session
	 */
	private boolean isSessionCookie(String name) {
		return name.equalsIgnoreCase("JSESSIONID") || name.equalsIgnoreCase("PHPSESSID")
				|| name.equalsIgnoreCase("ASPSESSIONID") || name.equalsIgnoreCase("ASP.NET_SessionId");
	}
	
	public HashMap getServers() {
		return servers;
	}

	public void setServers(HashMap servers) {
		this.servers = servers;
	}
	
	public String getDispatchCat() {
		return dispatchCat;
	}

	public void setDispatchCat(String dispatchCat) {
		this.dispatchCat = dispatchCat;
	}

	public String getDispatchColumn() {
		return dispatchColumn;
	}

	public void setDispatchColumn(String dispatchColumn) {
		this.dispatchColumn = dispatchColumn;
	}
	
	protected class DispatchServer implements Server {

		/**
		 * The domain name mapping
		 */
		private String domainName;

		/**
		 * The path mapping
		 */
		private String path;

		/**
		 * This servers id
		 */
		private String serverId;

		public DispatchServer(String domainName, String directory, String id) {
			this.domainName = domainName;
			this.path = directory;
			this.serverId = id;
		}

		/**
		 * Will wrap the request so the tailing .something, identifying the
		 * server, is removed from the request.
		 * 
		 * @see net.sf.j2ep.model.Server#preExecute(javax.servlet.http.HttpServletRequest)
		 */
		public HttpServletRequest preExecute(HttpServletRequest request) {
			return new ClusterRequestWrapper(request);
		}

		/**
		 * Will wrap the response so that sessions are rewritten to remove the
		 * tailing .something that indicated which server the session is linked
		 * to.
		 * 
		 * @see net.sf.j2ep.model.Server#postExecute(javax.servlet.http.HttpServletResponse)
		 */
		public HttpServletResponse postExecute(HttpServletResponse response) {
			return new ClusterResponseWrapper(response, serverId);
		}

		/**
		 * @see net.sf.j2ep.model.Server#getDomainName()
		 */
		public String getDomainName() {
			return domainName;
		}

		/**
		 * @see net.sf.j2ep.model.Server#getPath()
		 */
		public String getPath() {
			return path;
		}

		/**
		 * @see net.sf.j2ep.model.Server#getRule()
		 */
		public Rule getRule() {
			return DispatchCluster.this.getRule();
		}

		/**
		 * Returns this servers ID.
		 * 
		 * @return The server ID
		 */
		public String getServerId() {
			return serverId;
		}

		public void setConnectionExceptionRecieved(Exception e) {

		}
	}
}
